package backend.Algoritmos;
import java.util.*;
import java.util.Map.Entry;

import com.dp.client.dto.AlmacenDTO;
import com.dp.client.dto.Fecha;
import com.dp.client.dto.PaisDTO;
import com.dp.client.dto.PaqueteDTO;
import com.dp.client.servicios.paquete.IServiciosPaquete;
import com.dp.server.IServiciosAlmacenImpl;
import com.dp.server.IServiciosConexionImpl;
import com.dp.server.IServiciosGeograficosImpl;
import com.dp.server.IServiciosPaqueteImpl;
import com.dp.server.MantenimientoGenerico;
import com.dp.server.backend.RutaDeVueloBackend;
import com.dp.server.entities.PlanDeVuelo;

import backend.estructuras.DistribucionNormalConexion;
import backend.estructuras.common.*;


//todos los intervalos abiertos por la derecha

public abstract class Solver {
	//dia 0 es lunes
	protected Fecha fechaInicialSimulacion = null;
	protected Fecha fechaFinSimulacion = null;
	protected Fecha fechaInicioUniverso = null;
	protected ArrayList<Almacen> almacenes = new ArrayList<Almacen>();
	protected ArrayList<Conexion> conexiones = new ArrayList<Conexion>();
	protected ArrayList<Vuelo> vuelos = new ArrayList<Vuelo>();
	protected ArrayList<Paquete> paquetesEstimados = new ArrayList<Paquete>();
	protected ArrayList<Paquete> paquetesFijos = new ArrayList<Paquete>();
	protected ArrayList<Continente> continentes = new ArrayList<Continente>();
	protected ArrayList<Paquete> paquetesSinRuta = new ArrayList<>();
	protected ArrayList<Paquete> paquetes = new ArrayList<>();
	private String nombre = "ninguno";
	protected HashMap<Entry<Almacen, Almacen>, DistribucionNormalConexion> distribuciones = new HashMap<>();
	protected Solver(String nombre){
		this.nombre = nombre;
	}
	public String getNombre(){
		return nombre;
	}
	
	//TODO es aca
	void generaDistribuciones(Fecha fechaLimite){
		this.distribuciones = new HashMap<Entry<Almacen,Almacen>, DistribucionNormalConexion>();
		for(Almacen a : this.almacenes)
			for(Almacen b : this.almacenes)
				if(a != b)
					distribuciones.put(new AbstractMap.SimpleEntry<Almacen, Almacen>(a, b), new DistribucionNormalConexion(a,  b, this));
	}
	static Fecha duracionVueloIntracontinental = new Fecha(0, 12);
	static Fecha duracoinVueloIntercontinental = new Fecha(1, 0);
	
	static public Fecha getDuracionVueloIntracontinental(){
		return duracionVueloIntracontinental;
	}
	
	static public Fecha getDuracionVueloIntercontinental(){
		return duracoinVueloIntercontinental;
	}
	
	public void setFechaInicialSimulacion(Fecha fecha){
		fechaInicialSimulacion = fecha;
	}
	
	public void setFechaFinSimulacion(Fecha fecha){
		fechaFinSimulacion = fecha;
	}
	
	public void setFechaInicioUniverso(Fecha fecha){
		fechaInicioUniverso = fecha;
	}
	
	public Continente addContinente(String nombre){
		Continente continente = new Continente(nombre);
		continentes.add(continente);
		return continente;
	}
	public Almacen addAlmacen(String nombre, int capacidadInicial, Continente continente, int id, double latitud, double longitud){
		if(fechaInicialSimulacion == null)
			return null;
		Almacen almacen = new Almacen(nombre, continente, id, latitud, longitud);
		almacenes.add(almacen);
		almacen.addCambioDeCapacidad(capacidadInicial, fechaInicialSimulacion);
		continente.addAlmacen(almacen);
		return almacen;
	}
	
	
	public Conexion addConexion(Almacen origen, Almacen destino, int hora, boolean[] frecuencia, int capacidadInicial, int id){
		Conexion conexion = new Conexion(origen, destino, hora, frecuencia, capacidadInicial, id);
		conexiones.add(conexion);
		return conexion;
	}
	
	//los intervalos son abiertos en la derecha
	protected List<Vuelo> generaVuelosEnIntervaloDeTiempo(Fecha fechaInicioIntervalo, int horasIntervalo){
		Vector<Vuelo> agregados = new Vector<Vuelo>();
		int ID = 0;
		for(Conexion c : conexiones)
			for(int i = 0; i < 7; i++) if(c.ocurreEnDia(i)){
				Fecha primeraFecha = Fecha.getPrimeraFecha(fechaInicialSimulacion, i, c.getHoraDeVuelo());
				if(primeraFecha.compareTo(fechaInicialSimulacion) < 0) continue;
				while(primeraFecha.toHoras() - fechaInicioIntervalo.toHoras() < horasIntervalo){
					Vuelo vuelo = new Vuelo( new Fecha( primeraFecha.getDia() , primeraFecha.getHora() ), c, ++ID);
					this.vuelos.add(vuelo);
					agregados.add(vuelo);
					primeraFecha.addHoras(7 * 24);
					c.getOrigen().addVuelo(vuelo);
				}
			}
		return agregados;
	}
	
	void addPaqueteGenerico(Paquete paq){
		paq.getOrigen().addPaqueteOriginadoEnAlmacen(paq);
	}
	protected void addPaqueteFijo(Paquete paq){
		this.paquetesFijos.add(paq);
		addPaqueteGenerico(paq);
	}
	protected void addPaqueteEstimado(Paquete paq){
		this.paquetesEstimados.add(paq);
		addPaqueteGenerico(paq);
	}
	
	public abstract List<Paquete> generaPaquetesEstimadosParaIntervaloDeTiempo(Fecha fechaInicio, Fecha fechaFin);
	
	protected abstract Ruta generaRutaDePaquete(Paquete paquete);

	public List<Continente> getContinentes() {
		return this.continentes;
	}

	public Continente getContinentePorNombre(String nombre) {
		for(Continente c : continentes)
			if(c.getNombre().equals(nombre)) return c;
		return null;
	}

	public Almacen getAlmacenPorNombre(String nombre) {
		for(Almacen a : almacenes) 
			if(a.getNombre().equals(nombre)) return a;
		return null;
	}
	
	public abstract void generaYEjecutaRutasParaIntervaloDeTiempo(Fecha fechaInicio, Fecha fechaFin);
	
	public boolean EjecutaRutaDePaquete(Paquete p, Ruta r){
		if(r.getBottleNeck() == 0) return false;
		r.ejecutaRuta();
		p.setRuta(r);
		this.paquetesFijos.add(p);
		return true;
	}
	protected Vector<Paquete> getPaquetesEnIntervaloDeTiempo(Fecha fechaInicio, Fecha fechaFin){
		Paquete fakeInicio = new Paquete(null, null, fechaInicio, true);
		Paquete fakeFin = new Paquete(null, null, fechaFin, true);
		Vector< Paquete > ans = new Vector<>();
		for( Paquete p : paquetesEstimados ) if( fechaInicio.compareTo( p.getFechaRegistro() ) <= 0 &&
												 fechaFin.compareTo( p.getFechaRegistro() ) >= 0 ){
			ans.add( p );
		}
		for( Paquete p : paquetesFijos ) if( fechaInicio.compareTo( p.getFechaRegistro() ) <= 0 &&
												fechaFin.compareTo( p.getFechaRegistro() ) >= 0 ){
			ans.add( p );
		}
	//	SortedSet<Paquete> estimados = this.paquetesEstimados.subSet(fakeInicio, true, fakeFin, false);
	//	SortedSet<Paquete> fijos = this.paquetesFijos.subSet(fakeInicio, true, fakeFin, false);
	//	estimados.addAll(fijos);
		return ans;
	}
	
	protected void addPaqueteSinRuta(Paquete p){
		this.paquetesSinRuta.add(p);
	}
	
	@SuppressWarnings("unchecked")
	public List<Paquete> getPaquetesSinRuta(){
		return (List<Paquete>)this.paquetesSinRuta.clone();
	}
	public List<Almacen> getAlmacenes() {
		return this.almacenes;
	}
	public abstract void addDistribucion(Almacen origen, Almacen destino, int media,
			double varianza);
	
	
	public void cargarDatosDesdeBD(boolean paraSimulacion) {
		//Meter fecha inicial y final y de inicio del universo
		Fecha fechaInicio = new Fecha(0, 0);
		this.setFechaInicialSimulacion(fechaInicio);
		this.setFechaInicioUniverso(fechaInicio);
		//Meter fecha fin
		Fecha fechaFin = Fecha.nextDias(fechaInicio, 40);
		//Meter continentes
		IServiciosGeograficosImpl servgeo = new IServiciosGeograficosImpl();
		List<PaisDTO> paises = servgeo.listPaises();
		TreeSet<String> continentes = new TreeSet<>();
		for(PaisDTO p : paises) continentes.add(p.getContinente().getNombre());
		for(String cont : continentes){
			this.addContinente(cont);
		}
		//Meter almacenes
		IServiciosAlmacenImpl serv = new IServiciosAlmacenImpl();
		List<AlmacenDTO> almacenes = serv.listAlmacenes();
		for(AlmacenDTO a : almacenes){
			this.addAlmacen(a.getDescripcion(), a.getCapacidadMaxima(),
					this.getContinentePorNombre(a.getCiudad().getPais().getContinente().getNombre()), a.getId(), a.getLatitud(), a.getLongitud());
		}
		//Meter conexiones
		MantenimientoGenerico<com.dp.server.entities.Conexion> mantenimientoConexiones = new MantenimientoGenerico<>("Conexion");
		for(com.dp.server.entities.Conexion conexion : mantenimientoConexiones.list()){
			Almacen origen = this.getAlmacenPorId(conexion.getIdOrigen());
			Almacen destino = this.getAlmacenPorId(conexion.getIdDestino());
			int hora = conexion.getHoraSalida().getHours();
			int capacidadInicial = conexion.getCapacidad();
			this.addConexion(origen, destino, hora, conexion.getDiasActivos(), capacidadInicial, conexion.getId());
		}
		//meter vuelos
		
			MantenimientoGenerico<com.dp.server.entities.Vuelo> mantenimientoVuelos = new MantenimientoGenerico<>("Vuelo");
			List<com.dp.server.entities.Vuelo> vuelosBD = mantenimientoVuelos.list();
			for(com.dp.server.entities.Vuelo v : vuelosBD){
				Fecha fecha = Fecha.getFechaFromDate(v.getFechaHoraSalida());
				Conexion conexion = this.getConexionPorId(v.getIdConexion());
				Vuelo vuelo = new Vuelo(fecha, conexion, v.getId());
				this.vuelos.add(vuelo);
				conexion.getOrigen().addVuelo(vuelo);
			}
		
		//meter distribuciones y data historica
		/*for(int i = 0; i < this.getAlmacenes().size(); i++)
			for(int j = 0; j < this.getAlmacenes().size(); j++) if(i != j){
				String origen = this.getAlmacenes().get(i).getNombre();
				String destino = this.getAlmacenes().get(j).getNombre();
				int media = 2;
				double varianza = 0.5;
				this.addDistribucion(this.getAlmacenPorNombre(origen),
									   this.getAlmacenPorNombre(destino), media, varianza);
			}*/
		// meter paquetes
			IServiciosPaqueteImpl servPaquete = new IServiciosPaqueteImpl();
			MantenimientoGenerico<PlanDeVuelo> mant = new MantenimientoGenerico<>("PlanDeVuelo");
			for(PaqueteDTO p : servPaquete.listPaquetes()){
				int idruta = p.getPlanDeVuelo();
				System.out.println( "" + idruta);
				Paquete paquete = new Paquete(this.getAlmacenPorId(p.getAlmacenOrigen()),
					    this.getAlmacenPorId(p.getAlmacenDestino()),
					    Fecha.getFechaFromDate(p.getFechaRegistro()),
					    true);
				paquetes.add(paquete);
				if(idruta < 1) continue;

				PlanDeVuelo plan = mant.buscarID(idruta);
				List<Vuelo> vuelos= new ArrayList<Vuelo>();
				if(!paraSimulacion)
					for(int i = 1; i < plan.getReservas().length; i += 2)
						vuelos.add(this.getVueloById(plan.getReservas()[i]));
				Ruta ruta = new Ruta(vuelos, 
						paquete);
				ruta.ejecutaRuta();
			}
		this.generaDistribuciones(Fecha.getFechaFromDate(new Date()));
	}
	
	public Conexion getConexionPorId(int idConexion) {
		for(Conexion c : this.conexiones)
			if(c.getId() == idConexion) return c;
		return null;
	}
	
	public Almacen getAlmacenPorId(int id) {
		for(Almacen a : this.almacenes)
			if(a.getId() == id) return a;
		return null;
	}
	
	public RutaDeVueloBackend[] CalculaRutasParaPaquete(Paquete paquete) {
		Vector<Ruta> rutas_raw = generaRutasDePaquete(paquete);
		
		RutaDeVueloBackend[] ret = new RutaDeVueloBackend[rutas_raw.size()];
		for(int i = 0; i < rutas_raw.size(); i++){
			ret[i] = rutas_raw.get(i).toRutaDeVueloBackend();
		}
		return ret;
	}
	protected abstract Vector<Ruta> generaRutasDePaquete(Paquete paquete);

	public int getCapacidadDeAlmacenEnTiempoDado(Almacen almacen, Fecha fecha){
		return almacen.getCapacidadEnInstanteDeTiempo(fecha);
	}
	public int getBottleneckDeAlmacenEnTiempoDado(Almacen almacen, Fecha fecha){
		try{
			return almacen.getBottleNeckParaIntervaloDeTiempo(fecha, Fecha.nextHora(fecha));
		}catch(Exception ex){
			return -1;
		}
	}
	public int getEspacioLlenoDeAlmacenEnTiempoDado(Almacen almacen, Fecha fecha){
		return getCapacidadDeAlmacenEnTiempoDado(almacen, fecha) - 
			   getBottleneckDeAlmacenEnTiempoDado(almacen, fecha);
	}
	TreeMap<Integer, Vuelo> mapaVuelos = null;
	public Vuelo getVueloById(int idVuelo) {
		if(mapaVuelos == null){
			mapaVuelos = new TreeMap<Integer, Vuelo>();
			for(Vuelo v : this.vuelos) mapaVuelos.put(v.getId(), v);
		}
		return mapaVuelos.get(idVuelo);
	}
	public List<Vuelo> getVuelos() {
		return vuelos;
	}
	public List<Conexion> getConexiones() {
		return conexiones;
	}
	public List<Paquete> getPaquetes() {
		return this.paquetes;
	}
	public void limpiaEstadisticas() {
		for(Almacen a1 : almacenes) for(Almacen a2 : almacenes) if(a1 != a2){
			this.distribuciones.get(new AbstractMap.SimpleEntry<Almacen, Almacen>(a1, a2)).reset();
		}
	}
}
